/* Copyright (c) 2007, Dr. WPF
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 
 *   * The name Dr. WPF may not be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Dr. WPF ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Dr. WPF BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

namespace MyCompany.Core //DrWPF.Windows.Data
{
	using System;
	using System.Collections;
	using System.Collections.Generic;
	using System.Collections.ObjectModel;
	using System.Collections.Specialized;
	using System.ComponentModel;
	using System.Runtime.InteropServices;
	using System.Runtime.Serialization;

	[Serializable]
	public class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IDictionary, ICollection, IEnumerable, ISerializable, IDeserializationCallback, INotifyCollectionChanged, INotifyPropertyChanged {
		readonly Dictionary<TKey, TValue> _dictionaryCache = new Dictionary<TKey, TValue>();

		[NonSerialized]
		readonly SerializationInfo _siInfo;

		int _countCache;

		int _dictionaryCacheVersion;

		protected KeyedDictionaryEntryCollection<TKey> _keyedEntryCollection;

		int _version;

		public ObservableDictionary() {
			_keyedEntryCollection = new KeyedDictionaryEntryCollection<TKey>();
		}

		public ObservableDictionary(IDictionary<TKey, TValue> dictionary) {
			_keyedEntryCollection = new KeyedDictionaryEntryCollection<TKey>();

			foreach (var entry in dictionary)
				DoAddEntry(entry.Key, entry.Value);
		}

		public ObservableDictionary(IEqualityComparer<TKey> comparer) {
			_keyedEntryCollection = new KeyedDictionaryEntryCollection<TKey>(comparer);
		}

		public ObservableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) {
			_keyedEntryCollection = new KeyedDictionaryEntryCollection<TKey>(comparer);

			foreach (var entry in dictionary)
				DoAddEntry(entry.Key, entry.Value);
		}

		protected ObservableDictionary(SerializationInfo info, StreamingContext context) {
			_siInfo = info;
		}

		public IEqualityComparer<TKey> Comparer
		{
			get
			{
				return _keyedEntryCollection.Comparer;
			}
		}

		public int Count
		{
			get
			{
				return _keyedEntryCollection.Count;
			}
		}

		public Dictionary<TKey, TValue>.KeyCollection Keys
		{
			get
			{
				return TrueDictionary.Keys;
			}
		}

		public Dictionary<TKey, TValue>.ValueCollection Values
		{
			get
			{
				return TrueDictionary.Values;
			}
		}

		Dictionary<TKey, TValue> TrueDictionary
		{
			get
			{
				if (_dictionaryCacheVersion != _version) {
					_dictionaryCache.Clear();
					foreach (var entry in _keyedEntryCollection)
						_dictionaryCache.Add((TKey)entry.Key, (TValue)entry.Value);
					_dictionaryCacheVersion = _version;
				}
				return _dictionaryCache;
			}
		}

		public TValue this[TKey key]
		{
			get
			{
				return (TValue)_keyedEntryCollection[key].Value;
			}
			set
			{
				DoSetEntry(key, value);
			}
		}

		public virtual void OnDeserialization(object sender) {
			if (_siInfo != null) {
				var entries = (Collection<DictionaryEntry>)_siInfo.GetValue("entries", typeof(Collection<DictionaryEntry>));
				foreach (var entry in entries)
					AddEntry((TKey)entry.Key, (TValue)entry.Value);
			}
		}

		int ICollection.Count
		{
			get
			{
				return _keyedEntryCollection.Count;
			}
		}

		bool IDictionary.IsFixedSize
		{
			get
			{
				return false;
			}
		}

		bool IDictionary.IsReadOnly
		{
			get
			{
				return false;
			}
		}

		bool ICollection.IsSynchronized
		{
			get
			{
				return ((ICollection)_keyedEntryCollection).IsSynchronized;
			}
		}

		ICollection IDictionary.Keys
		{
			get
			{
				return Keys;
			}
		}

		object ICollection.SyncRoot
		{
			get
			{
				return ((ICollection)_keyedEntryCollection).SyncRoot;
			}
		}

		ICollection IDictionary.Values
		{
			get
			{
				return Values;
			}
		}

		object IDictionary.this[object key]
		{
			get
			{
				return _keyedEntryCollection[(TKey)key].Value;
			}
			set
			{
				DoSetEntry((TKey)key, (TValue)value);
			}
		}

		void IDictionary.Add(object key, object value) {
			DoAddEntry((TKey)key, (TValue)value);
		}

		void IDictionary.Clear() {
			DoClearEntries();
		}

		bool IDictionary.Contains(object key) {
			return _keyedEntryCollection.Contains((TKey)key);
		}

		void ICollection.CopyTo(Array array, int index) {
			((ICollection)_keyedEntryCollection).CopyTo(array, index);
		}

		IDictionaryEnumerator IDictionary.GetEnumerator() {
			return new Enumerator<TKey, TValue>(this, true);
		}

		void IDictionary.Remove(object key) {
			DoRemoveEntry((TKey)key);
		}

		int ICollection<KeyValuePair<TKey, TValue>>.Count
		{
			get
			{
				return _keyedEntryCollection.Count;
			}
		}

		bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly
		{
			get
			{
				return false;
			}
		}

		ICollection<TKey> IDictionary<TKey, TValue>.Keys
		{
			get
			{
				return Keys;
			}
		}

		ICollection<TValue> IDictionary<TKey, TValue>.Values
		{
			get
			{
				return Values;
			}
		}

		TValue IDictionary<TKey, TValue>.this[TKey key]
		{
			get
			{
				return (TValue)_keyedEntryCollection[key].Value;
			}
			set
			{
				DoSetEntry(key, value);
			}
		}

		void IDictionary<TKey, TValue>.Add(TKey key, TValue value) {
			DoAddEntry(key, value);
		}

		void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> kvp) {
			DoAddEntry(kvp.Key, kvp.Value);
		}

		void ICollection<KeyValuePair<TKey, TValue>>.Clear() {
			DoClearEntries();
		}

		bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> kvp) {
			return _keyedEntryCollection.Contains(kvp.Key);
		}

		bool IDictionary<TKey, TValue>.ContainsKey(TKey key) {
			return _keyedEntryCollection.Contains(key);
		}

		void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int index) {
			if (array == null)
				throw new ArgumentNullException("CopyTo() failed:  array parameter was null");
			if ((index < 0) || (index > array.Length))
				throw new ArgumentOutOfRangeException("CopyTo() failed:  index parameter was outside the bounds of the supplied array");
			if ((array.Length - index) < _keyedEntryCollection.Count)
				throw new ArgumentException("CopyTo() failed:  supplied array was too small");

			foreach (var entry in _keyedEntryCollection)
				array[index++] = new KeyValuePair<TKey, TValue>((TKey)entry.Key, (TValue)entry.Value);
		}

		IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() {
			return new Enumerator<TKey, TValue>(this, false);
		}

		IEnumerator IEnumerable.GetEnumerator() {
			return GetEnumerator();
		}

		bool IDictionary<TKey, TValue>.Remove(TKey key) {
			return DoRemoveEntry(key);
		}

		bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> kvp) {
			return DoRemoveEntry(kvp.Key);
		}

		bool IDictionary<TKey, TValue>.TryGetValue(TKey key, out TValue value) {
			return TryGetValue(key, out value);
		}

		event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged
		{
			add
			{
				CollectionChanged += value;
			}
			remove
			{
				CollectionChanged -= value;
			}
		}

		event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
		{
			add
			{
				PropertyChanged += value;
			}
			remove
			{
				PropertyChanged -= value;
			}
		}

		public virtual void GetObjectData(SerializationInfo info, StreamingContext context) {
			if (info == null)
				throw new ArgumentNullException("info");

			var entries = new Collection<DictionaryEntry>();
			foreach (var entry in _keyedEntryCollection)
				entries.Add(entry);
			info.AddValue("entries", entries);
		}

		protected virtual event NotifyCollectionChangedEventHandler CollectionChanged;

		protected virtual event PropertyChangedEventHandler PropertyChanged;

		public void Add(TKey key, TValue value) {
			DoAddEntry(key, value);
		}

		public void Clear() {
			DoClearEntries();
		}

		public bool ContainsKey(TKey key) {
			return _keyedEntryCollection.Contains(key);
		}

		public bool ContainsValue(TValue value) {
			return TrueDictionary.ContainsValue(value);
		}

		public IEnumerator GetEnumerator() {
			return new Enumerator<TKey, TValue>(this, false);
		}

		public bool Remove(TKey key) {
			return DoRemoveEntry(key);
		}

		public bool TryGetValue(TKey key, out TValue value) {
			bool result = _keyedEntryCollection.Contains(key);
			value = result ? (TValue)_keyedEntryCollection[key].Value : default(TValue);
			return result;
		}

		protected virtual bool AddEntry(TKey key, TValue value) {
			_keyedEntryCollection.Add(new DictionaryEntry(key, value));
			return true;
		}

		protected virtual bool ClearEntries() {
			// check whether there are entries to clear
			bool result = (Count > 0);
			if (result) // if so, clear the dictionary
				_keyedEntryCollection.Clear();
			return result;
		}

		protected int GetIndexAndEntryForKey(TKey key, out DictionaryEntry entry) {
			entry = new DictionaryEntry();
			int index = -1;
			if (_keyedEntryCollection.Contains(key)) {
				entry = _keyedEntryCollection[key];
				index = _keyedEntryCollection.IndexOf(entry);
			}
			return index;
		}

		protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) {
			if (CollectionChanged != null)
				CollectionChanged(this, args);
		}

		protected virtual void OnPropertyChanged(string name) {
			if (PropertyChanged != null)
				PropertyChanged(this, new PropertyChangedEventArgs(name));
		}

		protected virtual bool RemoveEntry(TKey key) {
			// remove the entry
			return _keyedEntryCollection.Remove(key);
		}

		protected virtual bool SetEntry(TKey key, TValue value) {
			bool keyExists = _keyedEntryCollection.Contains(key);

			// if identical key/value pair already exists, nothing to do
			if (keyExists && value.Equals((TValue)_keyedEntryCollection[key].Value))
				return false;

			// otherwise, remove the existing entry
			if (keyExists)
				_keyedEntryCollection.Remove(key);

			// add the new entry
			_keyedEntryCollection.Add(new DictionaryEntry(key, value));

			return true;
		}

		void DoAddEntry(TKey key, TValue value) {
			if (AddEntry(key, value)) {
				_version++;

				DictionaryEntry entry;
				int index = GetIndexAndEntryForKey(key, out entry);
				FireEntryAddedNotifications(entry, index);
			}
		}

		void DoClearEntries() {
			if (ClearEntries()) {
				_version++;
				FireResetNotifications();
			}
		}

		bool DoRemoveEntry(TKey key) {
			DictionaryEntry entry;
			int index = GetIndexAndEntryForKey(key, out entry);

			bool result = RemoveEntry(key);
			if (result) {
				_version++;
				if (index > -1)
					FireEntryRemovedNotifications(entry, index);
			}

			return result;
		}

		void DoSetEntry(TKey key, TValue value) {
			DictionaryEntry entry;
			int index = GetIndexAndEntryForKey(key, out entry);

			if (SetEntry(key, value)) {
				_version++;

				// if prior entry existed for this key, fire the removed notifications
				if (index > -1) {
					FireEntryRemovedNotifications(entry, index);

					// force the property change notifications to fire for the modified entry
					_countCache--;
				}

				// then fire the added notifications
				index = GetIndexAndEntryForKey(key, out entry);
				FireEntryAddedNotifications(entry, index);
			}
		}

		void FireEntryAddedNotifications(DictionaryEntry entry, int index) {
			// fire the relevant PropertyChanged notifications
			FirePropertyChangedNotifications();

			// fire CollectionChanged notification
			if (index > -1)
				OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new KeyValuePair<TKey, TValue>((TKey)entry.Key, (TValue)entry.Value), index));
			else
				OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
		}

		void FireEntryRemovedNotifications(DictionaryEntry entry, int index) {
			// fire the relevant PropertyChanged notifications
			FirePropertyChangedNotifications();

			// fire CollectionChanged notification
			if (index > -1)
				OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new KeyValuePair<TKey, TValue>((TKey)entry.Key, (TValue)entry.Value), index));
			else
				OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
		}

		void FirePropertyChangedNotifications() {
			if (Count != _countCache) {
				_countCache = Count;
				OnPropertyChanged("Count");
				OnPropertyChanged("Item[]");
				OnPropertyChanged("Keys");
				OnPropertyChanged("Values");
			}
		}

		void FireResetNotifications() {
			// fire the relevant PropertyChanged notifications
			FirePropertyChangedNotifications();

			// fire CollectionChanged notification
			OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
		}

		[Serializable]
		[StructLayout(LayoutKind.Sequential)]
		public struct Enumerator<TKey1, TValue1> : IEnumerator<KeyValuePair<TKey1, TValue1>>, IDisposable, IDictionaryEnumerator, IEnumerator {
			#region constructors

			internal Enumerator(ObservableDictionary<TKey1, TValue1> dictionary, bool isDictionaryEntryEnumerator) {
				_dictionary = dictionary;
				_version = dictionary._version;
				_index = -1;
				_isDictionaryEntryEnumerator = isDictionaryEntryEnumerator;
				_current = new KeyValuePair<TKey1, TValue1>();
			}

			#endregion constructors

			#region properties

			#region public

			public KeyValuePair<TKey1, TValue1> Current
			{
				get
				{
					ValidateCurrent();
					return _current;
				}
			}

			#endregion public

			#endregion properties

			#region methods

			#region public

			public void Dispose() {}

			public bool MoveNext() {
				ValidateVersion();
				_index++;
				if (_index < _dictionary._keyedEntryCollection.Count) {
					_current = new KeyValuePair<TKey1, TValue1>((TKey1)_dictionary._keyedEntryCollection[_index].Key, (TValue1)_dictionary._keyedEntryCollection[_index].Value);
					return true;
				}
				_index = -2;
				_current = new KeyValuePair<TKey1, TValue1>();
				return false;
			}

			#endregion public

			#region private

			void ValidateCurrent() {
				if (_index == -1)
					throw new InvalidOperationException("The enumerator has not been started.");
				if (_index == -2)
					throw new InvalidOperationException("The enumerator has reached the end of the collection.");
			}

			void ValidateVersion() {
				if (_version != _dictionary._version)
					throw new InvalidOperationException("The enumerator is not valid because the dictionary changed.");
			}

			#endregion private

			#endregion methods

			#region IEnumerator implementation

			object IEnumerator.Current
			{
				get
				{
					ValidateCurrent();
					if (_isDictionaryEntryEnumerator)
						return new DictionaryEntry(_current.Key, _current.Value);
					return new KeyValuePair<TKey1, TValue1>(_current.Key, _current.Value);
				}
			}

			void IEnumerator.Reset() {
				ValidateVersion();
				_index = -1;
				_current = new KeyValuePair<TKey1, TValue1>();
			}

			#endregion IEnumerator implemenation

			#region IDictionaryEnumerator implemenation

			DictionaryEntry IDictionaryEnumerator.Entry
			{
				get
				{
					ValidateCurrent();
					return new DictionaryEntry(_current.Key, _current.Value);
				}
			}

			object IDictionaryEnumerator.Key
			{
				get
				{
					ValidateCurrent();
					return _current.Key;
				}
			}

			object IDictionaryEnumerator.Value
			{
				get
				{
					ValidateCurrent();
					return _current.Value;
				}
			}

			#endregion

			#region fields

			readonly ObservableDictionary<TKey1, TValue1> _dictionary;

			readonly int _version;

			int _index;

			KeyValuePair<TKey1, TValue1> _current;

			readonly bool _isDictionaryEntryEnumerator;

			#endregion fields
		}

		protected class KeyedDictionaryEntryCollection<TKey1> : KeyedCollection<TKey1, DictionaryEntry> {
			public KeyedDictionaryEntryCollection() {}

			public KeyedDictionaryEntryCollection(IEqualityComparer<TKey1> comparer) : base(comparer) {}

			protected override TKey1 GetKeyForItem(DictionaryEntry entry) {
				return (TKey1)entry.Key;
			}
		}
	}
}
